﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using thailandProject.ViewModel;

namespace thailandProject.Models
{
    /// <summary>
    /// Initially, it takes a hard-coded template and a PropertyMatch list with a clientId to
    /// generate "personalized" email text. This can later be implemented to run from a stored
    /// template
    /// </summary>
    public class EmailGenerator
    {
        public static string generateEmailText(List<PropertyMatch> properties, string clientName)
        {
            string message = "";
            try
            {
                DateTime start = determineEarliestDate(properties);
                DateTime end = determineLatestDate(properties);
                bool onlyPartial = determineIfOnlyPartial(properties);

                message = "Dear " + clientName + ",\n\n";
                message += "Thank you for your interest in Chiang Mai Apartment Rentals. ";

                if (properties.Count == 0)
                {
                    //No properties were found for the date range. Just display a generic empty email.
                    message += "Unfortunately, I do not have any apartments available for ";
                    message += "the dates you wished to stay. ";
                }
                else if (!onlyPartial)
                {
                    if (properties.Count > 1)
                        message += "I recommend you stay in one of the apartments listed below during your stay from ";
                    else
                        message += "I recommend you stay in the following apartment from ";

                    message += getDateString(start) + " to " + getDateString(end) + ":\n\n";

                    //Now, for each apartment in the list that is not a partial match
                    int totalCompleteMatches = 0;
                    foreach (PropertyMatch pm in properties.Where(p => !p.isPartial))
                    {
                        message += "   Apartment " + pm.property.propName + " - " + pm.property.propHyperlink + "\n";
                        message += "      Price - ฿" + pm.quotedPrice + "\n\n";
                        totalCompleteMatches++;
                    }

                    if (totalCompleteMatches > 0)
                    {
                        if (totalCompleteMatches + 2 <= properties.Count)
                        {
                            //Meaning that there are at least two or more partial matches
                            message += "Alternatively, I have the following properties also available. While you won't be able to stay in ";
                            message += "one apartment, you will be able to see more of the city during your stay:\n\n";
                            foreach (PropertyMatch pm in properties.Where(p => p.isPartial))
                            {
                                message += "   Apartment " + pm.property.propName + " - " + pm.property.propHyperlink + "\n";
                                message += "      Available for: " + getDateString(pm.startDate) + " to " + getDateString(pm.endDate) + "\n";
                                message += "      Price - ฿" + pm.quotedPrice + "\n\n";
                            }
                        }
                        else if (totalCompleteMatches + 1 == properties.Count)
                        {
                            //Meaning that there is only one partial match
                            message += "I also have the following property available for ";
                            foreach (PropertyMatch pm in properties.Where(p => p.isPartial))
                            {
                                message += getDateString(pm.startDate) + " to " + getDateString(pm.endDate) + ":\n\n";
                                message += "   Apartment " + pm.property.propName + " - " + pm.property.propHyperlink + "\n";
                                message += "      Price - ฿" + pm.quotedPrice + "\n\n";
                            }
                        }
                        //else do nothing
                    }//End to if
                }//End to if !onlyPartial
                else
                {
                    //There were no complete matches
                    if (properties.Count >= 2)
                    {
                        //if there are at least two or more partial matches
                        message += " While I do not have any apartments available for your entire stay, I do have some properties that are partly available. ";
                        message += "The advantage to this is that you will be able to see more of the city during your stay:\n\n";
                        foreach (PropertyMatch pm in properties.Where(p => p.isPartial))
                        {
                            message += "   Apartment " + pm.property.propName + " - " + pm.property.propHyperlink + "\n";
                            message += "      Available for: " + getDateString(pm.startDate) + " to " + getDateString(pm.endDate) + "\n";
                            message += "      Price - ฿" + pm.quotedPrice + "\n\n";
                        }
                    }
                    else
                    {
                        //Just display the only partial match
                        foreach (PropertyMatch pm in properties.Where(p => p.isPartial))
                        {
                            message += "I only have the following apartment available:\n\n";
                            message += "   Apartment " + pm.property.propName + " - " + pm.property.propHyperlink + "\n";
                            message += "      Available for: " + getDateString(pm.startDate) + " to " + getDateString(pm.endDate) + "\n";
                            message += "      Price - ฿" + pm.quotedPrice + "\n\n";
                        }
                    }//End to else
                } //End to else

                if (properties.Count != 0)
                {
                    bool onHold = false;
                    foreach (PropertyMatch pm in properties)
                        if (pm.isOnHold)
                        {
                            onHold = true;
                            break;
                        }

                    if (onHold)
                    {
                        message += "Please be aware that others, wanting to stay at a similar time, have expressed interest in ";

                        if (properties.Count > 1 && onlyPartial)
                            message += "these apartments. If you are serious about wanting these apartments, ";
                        else if (properties.Count > 1)
                            message += "these apartments. If you are serious about wanting one of them, ";
                        else
                            message += "this apartment. If you are serious about wanting this apartment, ";

                        message += "please make your deposit as soon as possible so I can reserve ";

                        if (properties.Count > 1 && onlyPartial)
                            message += "them for you. ";
                        else
                            message += "it for you. ";
                    }

                    if (properties.Count == 1)
                        message += "This is the only apartment I have available for the dates of your stay. Let me know if it suits your taste.";
                    else if (properties.Count > 1 && onlyPartial)
                        message += "Please look over the apartments I have listed above and let me know if they suit your tastes.";
                    else if (properties.Count != 0)
                        message += "Please look over the apartments I have listed above and let me know what best suits your tastes.";
                }
                else
                {
                    message += "I apologize for being unable to accomodate you at this time. Please remember us for a future stay and we will ";
                    message += "try our best to have something available for you.";
                }

                message += "\n\n-Richard Katze";
            }
            catch (Exception ex)
            {
                ErrorLog.logError(ex);
            }

            return message; //Phew, that was a fun logic exercise...
        }

        private static bool determineIfOnlyPartial(List<PropertyMatch> properties)
        {
            try
            {
                foreach (PropertyMatch pm in properties)
                {
                    if (!pm.isPartial)
                    {
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                ErrorLog.logError(ex);
            }

            return true;
        }

        private static string getDateString(DateTime time)
        {
            string rt = "";
            try
            {
                switch (time.Month)
                {
                    case 1: rt += "January"; break;
                    case 2: rt += "February"; break;
                    case 3: rt += "March"; break;
                    case 4: rt += "April"; break;
                    case 5: rt += "May"; break;
                    case 6: rt += "June"; break;
                    case 7: rt += "July"; break;
                    case 8: rt += "August"; break;
                    case 9: rt += "September"; break;
                    case 10: rt += "October"; break;
                    case 11: rt += "November"; break;
                    case 12: rt += "December"; break;
                }

                rt += " " + time.Day;

                switch (time.Day)
                {
                    case 1: rt += "st"; break;
                    case 2: rt += "nd"; break;
                    case 3: rt += "rd"; break;
                    case 21: rt += "st"; break;
                    case 22: rt += "nd"; break;
                    case 23: rt += "rd"; break;
                    case 31: rt += "st"; break;
                }

                rt += ", " + time.Year;
            }
            catch (Exception ex)
            {
                ErrorLog.logError(ex);
            }

            return rt;
        }

        private static DateTime determineEarliestDate(List<PropertyMatch> list)
        {
            DateTime rt = new DateTime();
            try
            {
                rt = DateTime.MaxValue;

                foreach (PropertyMatch pm in list)
                {
                    if (rt > pm.startDate)
                        rt = pm.startDate;
                }
            }
            catch (Exception ex)
            {
                ErrorLog.logError(ex);
            }

            return rt;
        }

        private static DateTime determineLatestDate(List<PropertyMatch> list)
        {
            DateTime rt = new DateTime();
            try
            {
                rt = DateTime.MinValue;

                foreach (PropertyMatch pm in list)
                {
                    if (rt < pm.endDate)
                        rt = pm.endDate;
                }
            }
            catch (Exception ex)
            {
                ErrorLog.logError(ex);
            }

            return rt;
        }

        public static string generateRecoveryEmail(string name, string username, string password)
        {
            string rt = "";

            try
            {
                rt += "Dear " + name + ",\n\n";

                rt += "A forgotten account was recently requested . If you did not request this email, please disregard.\n\n";

                rt += "username: " + username + "\npassword: " + password + "\n\n";

                rt += "We would strongly recommend changing your password. Thank you.";
            }
            catch (Exception ex)
            {
                ErrorLog.logError(ex);
            }

            return rt;
        }

        public static string generateDummyText()
        {
            string rt = "";

            try
            {
                rt += "An error must have occurred. Try pressing your browser's back button and try again.";
            }
            catch (Exception e)
            {
                ErrorLog.logError(e);
            }

            return rt;
        }

        public static string onDepositMade(string clientName, PAYMENT payment)
        {
            string message = "";

            //List information about the deposit
            message += "Dear " + clientName + ",\n\n";

            message += "I have received your payment of " + formatAsCurrency(payment.paymentAmount) + ". ";

            message += "Due to third party fees, please be aware that the amount I received may not be the amount that you sent to me.\n\n";

            message += "The amount you currently owe is: " + formatAsCurrency(payment.RESERVATION.resQuotedPrice - payment.paymentAmount) + ".\n\n";

            message += "The remaining balance will be paid when you arrive on " + getDateString(payment.RESERVATION.resStartDate) + ". ";
            message += "Thank you for staying with us. I look forward to meeting you.\n\n";

            message += "-Richard Katze";

            return message;
        }

        private static string formatAsCurrency(decimal amount)
        {
            string rt = "฿" + amount.ToString();

            //Find the decimal point. The decimal point + 2 should be equal to the string's length - 1
            int decLoc = rt.IndexOf('.');

            if (decLoc == -1)
                return rt; //If no decimal exists, the number is technically formatted... yay!
            if (decLoc + 3 < rt.Length - 1)
            {
                //The decimal place extends past two digits
                //I'm not going to bother with rounding
                rt = rt.Substring(0, decLoc + 3);
            }

            return rt;
        }

        public static string onRefundMade(PAYMENT payment, string clientName)
        {
            string message = "";

            message += "Dear " + clientName + ", \n\n";

            message += "I have refunded your payment of " + formatAsCurrency(payment.paymentAmount) + ". ";

            message += "This amount only covers the amount I have received, so it may be more depending on if third party fees were ";
            message += "refunded as well. ";

            message += "I apologize for any inconvenience this has caused you.\n\n";

            message += "-Richard Katze";

            return message;
        }

        public static string onReservationDrop(List<RESERVATION> droppedRes, string clientName)
        {
            string message = "";

            message += "Dear " + clientName + ",\n\n";

            if (droppedRes.Count == 1)
            {
                message += "Your reservation for " + getDateString(droppedRes.First().resStartDate) + " to ";
                message += getDateString(droppedRes.First().resEndDate) + " on property " + droppedRes.First().PROPERTY.propName + " (";
                message += droppedRes.First().PROPERTY.propHyperlink + ") has been cancelled due to another ";
                message += "client making a deposit on this property with a similar time range.\n\n";
            }
            else
            {
                message += "Your reservation for the follow properties have been cancelled due to other clients making a ";
                message += "deposit for these properties for a similar time:";

                foreach (RESERVATION r in droppedRes)
                {
                    message += "   Apartment " + r.PROPERTY.propName + " - " + r.PROPERTY.propHyperlink + "\n";
                    message += "      Stay times: " + getDateString(r.resStartDate) + " to " + getDateString(r.resEndDate) + "\n";
                    message += "\n";
                }
            }

            message += "I apologize for any inconvenience this has caused. If you would like to see if I have another property available, ";
            message += "please reply and I will do my best to accommodate you. Thank you for your interest in Chiang Mail Apartment Rentals.";

            message += "\n\n-Richard Katze";

            return message;
        }

        public static string onReservationEdit(RESERVATION oldr, RESERVATION newr, string clientName)
        {
            string message = "";

            int totalChanges = 0;

            message += "Dear " + clientName + ",\n\n";

            message += "totalChanges";
          
            if (oldr.resStartDate != newr.resStartDate || oldr.resEndDate != newr.resEndDate)
            {
              message += "Your original reservation was from " + getDateString(oldr.resStartDate) + " to " + 
                getDateString(oldr.resEndDate) + ".\n";
              message += "The new reservation is from " + getDateString(newr.resStartDate) + " to " +
                getDateString(newr.resEndDate) + ".\n\n";

              totalChanges += 1;
            }

            if (oldr.resQuotedPrice != newr.resQuotedPrice)
            {
              message += "Your original quoted price for your reservation was " + formatAsCurrency(oldr.resQuotedPrice) + ".  " +
                "The new quoted price for your reservation is " + formatAsCurrency(newr.resQuotedPrice) + ".  \n\n";

              totalChanges += 1;
            }

            if (newr.resIsHeld && !oldr.resIsHeld)
            {
              message += "Your reservation between " + getDateString(newr.resStartDate) + " and " +
                getDateString(newr.resEndDate) + " for " + newr.PROPERTY.propName + 
                " has been confirmed and is being held for you.\n\n";
              totalChanges += 1; 
            }
            else if (!newr.resIsHeld && oldr.resIsHeld)
            {
              message += "Your reservation between " + getDateString(newr.resStartDate) + " and " +
                getDateString(newr.resEndDate) + " for " + newr.PROPERTY.propName +
                " is no longer being held and will require a deposit if you wish to keep your reservation.\n\n";

              totalChanges += 1;
            }

            if (totalChanges == 1)
            {
              message = message.Replace("totalChanges", "I just wanted to confirm the change in your reservation. \n\n");
            }
            else if (totalChanges > 1)
            {
              string number = null;

              switch(totalChanges)
              {
                case 2 : number = "two";
                  break;
                case 3 : number = "three";
                  break;
              }

              message = message.Replace("totalChanges", "I just wanted to confirm " + number + " changes in your reservation. \n\n"); 
            }

            message += "Please let me know if these changes are acceptable.";

            message += "\n\n-Richard Katze";

            return message;
        }

        public static string generateOnDepositMade(EmailTemplateModel template, string clientName, PAYMENT payment)
        {
            Dictionary<string, string> tagList = new Dictionary<string, string>();
            tagList.Add("[clientName]", clientName);
            tagList.Add("[quotedPrice]", formatAsCurrency(payment.RESERVATION.resQuotedPrice));
            tagList.Add("[remainingAmount]", formatAsCurrency(payment.RESERVATION.resQuotedPrice - payment.paymentAmount));
            tagList.Add("[paymentAmount]", formatAsCurrency(payment.paymentAmount));
            tagList.Add("[resStartDate]", getDateString(payment.RESERVATION.resStartDate));
            tagList.Add("[resEndDate]", getDateString(payment.RESERVATION.resEndDate));
            //tagList.Add("[propName]", payment.RESERVATION.PROPERTY.propName);
            //tagList.Add("[propHyperlink]", payment.RESERVATION.PROPERTY.propHyperlink);

            string rt = ""; 

            //This isn't very complex, so just paste all the information together and swap as necessary
            foreach (KeyValuePair<string, DictData> kvp in template.sentenceList)
            {
                rt += kvp.Value.value;
            }

            //Swap out any tags that are found
            foreach (KeyValuePair<string, string> kvp in tagList)
            {
                rt = rt.Replace(kvp.Key, kvp.Value);
            }

            return rt;
        }

        public static string generateOnRefundMade(EmailTemplateModel template, string clientName, PAYMENT payment)
        {
            Dictionary<string, string> tagList = new Dictionary<string, string>();
            tagList.Add("[clientName]", clientName);
            //tagList.Add("[quotedPrice]", formatAsCurrency(payment.RESERVATION.resQuotedPrice));
            //tagList.Add("[remainingAmount]", formatAsCurrency(payment.RESERVATION.resQuotedPrice - payment.paymentAmount));
            tagList.Add("[paymentAmount]", formatAsCurrency(payment.paymentAmount));
            //tagList.Add("[resStartDate]", getDateString(payment.RESERVATION.resStartDate));
            //tagList.Add("[resEndDate]", getDateString(payment.RESERVATION.resEndDate));
            //tagList.Add("[propName]", payment.RESERVATION.PROPERTY.propName);
            //tagList.Add("[propHyperlink]", payment.RESERVATION.PROPERTY.propHyperlink);

            string rt = "";

            //This isn't very complex, so just paste all the information together and swap as necessary
            foreach (KeyValuePair<string, DictData> kvp in template.sentenceList)
            {
                rt += kvp.Value.value;
            }

            //Swap out any tags that are found
            foreach (KeyValuePair<string, string> kvp in tagList)
            {
                rt = rt.Replace(kvp.Key, kvp.Value);
            }

            return rt;
        }

        public static string generateOnReservationEdit(EmailTemplateModel template, string clientName, RESERVATION oldr, RESERVATION newr)
        {
            Dictionary<string, string> tagList = new Dictionary<string, string>();
            tagList.Add("[clientName]", clientName);
            tagList.Add("[resOldStartDate]", getDateString(oldr.resStartDate));
            tagList.Add("[resOldEndDate]", getDateString(oldr.resEndDate));
            tagList.Add("[resOldQuotedPrice]", formatAsCurrency(oldr.resQuotedPrice));
            tagList.Add("[resQuotedPrice]", formatAsCurrency(newr.resQuotedPrice));
            tagList.Add("[resStartDate]", getDateString(newr.resStartDate));
            tagList.Add("[resEndDate]", getDateString(newr.resEndDate));
            tagList.Add("[propName]", newr.PROPERTY.propName);
            //tagList.Add("[propName]", payment.RESERVATION.PROPERTY.propName);
            tagList.Add("[propHyperlink]", newr.PROPERTY.propHyperlink);

            string rt = "";
            DictData o;

            template.sentenceList.TryGetValue("Introduction", out o);
            rt += o.value;

            if (oldr.resStartDate != newr.resStartDate || oldr.resEndDate != newr.resEndDate)
            {
                //The date has changed
                template.sentenceList.TryGetValue("Reservation Date Changed", out o);
                rt += o.value;
            }

            if (oldr.resQuotedPrice != newr.resQuotedPrice)
            {
                //The quoted price has changed
                template.sentenceList.TryGetValue("Reservation Quoted Price Changed", out o);
                rt += o.value;
            }

            if (newr.resIsHeld && !oldr.resIsHeld)
            {
                //The reservation was changed to being confirmed
                template.sentenceList.TryGetValue("Reservation Confirmation Changed to Unconfirmed", out o);
                rt += o.value;
            }
            else if (!newr.resIsHeld && oldr.resIsHeld)
            {
                //The reservation was changed to being unconfirmed
                template.sentenceList.TryGetValue("Reservation Confirmation Changed to Confirmed", out o);
                rt += o.value;
            }

            template.sentenceList.TryGetValue("Summary", out o);
            rt += o.value;

            template.sentenceList.TryGetValue("Signature", out o);
            rt += o.value;

            //Swap out any tags that are found
            foreach (KeyValuePair<string, string> kvp in tagList)
            {
                rt = rt.Replace(kvp.Key, kvp.Value);
            }

            return rt;
        }

        public static string generateOnReservationDrop(EmailTemplateModel template, string clientName, List<RESERVATION> droppedRes)
        {
            Dictionary<string, string> tagList = new Dictionary<string, string>();
            //tagList.Add("[propName]", payment.RESERVATION.PROPERTY.propName);
            //tagList.Add("[propHyperlink]", payment.RESERVATION.PROPERTY.propHyperlink);

            string rt = "";
            DictData o;

            template.sentenceList.TryGetValue("Introduction", out o);
            rt += o.value;

            if (droppedRes.Count() > 1)
            {
                //Two or more reservations were dropped
                template.sentenceList.TryGetValue("Multiple Properties Cancelled", out o);
                rt += o.value;

                //Get the property information model for each dropped property
                foreach (RESERVATION r in droppedRes)
                {
                    tagList.Clear();
                    tagList.Add("[resQuotedPrice]", formatAsCurrency(r.resQuotedPrice));
                    tagList.Add("[resStartDate]", getDateString(r.resStartDate));
                    tagList.Add("[resEndDate]", getDateString(r.resEndDate));
                    tagList.Add("[propName]", r.PROPERTY.propName);
                    tagList.Add("[propHyperlink]", r.PROPERTY.propHyperlink);

                    template.sentenceList.TryGetValue("Property Information Model", out o);

                    string temp = o.value;
                    foreach (KeyValuePair<string, string> kvp in tagList)
                    {
                        temp = temp.Replace(kvp.Key, kvp.Value);
                    }

                    rt += temp;
                }
            }//End to two or more res dropped
            else
            {
                //One res was dropped
                RESERVATION r = droppedRes.First();

                tagList.Clear();
                tagList.Add("[resQuotedPrice]", formatAsCurrency(r.resQuotedPrice));
                tagList.Add("[resStartDate]", getDateString(r.resStartDate));
                tagList.Add("[resEndDate]", getDateString(r.resEndDate));
                tagList.Add("[propName]", r.PROPERTY.propName);
                tagList.Add("[propHyperlink]", r.PROPERTY.propHyperlink);

                template.sentenceList.TryGetValue("Single Property Cancelled", out o);

                string temp = o.value;
                foreach (KeyValuePair<string, string> kvp in tagList)
                {
                    temp = temp.Replace(kvp.Key, kvp.Value);
                }

                rt += temp;
            }

            tagList.Add("[clientName]", clientName);

            template.sentenceList.TryGetValue("Summary", out o);
            rt += o.value;

            template.sentenceList.TryGetValue("Signature", out o);
            rt += o.value;

            //Swap out any tags that are found
            foreach (KeyValuePair<string, string> kvp in tagList)
            {
                rt = rt.Replace(kvp.Key, kvp.Value);
            }

            return rt;
        }

        public static string generateOnPropInquiry(EmailTemplateModel template, string clientName, List<PropertyMatch> properties)
        {
            Dictionary<string, string> tagList = new Dictionary<string, string>();
            //tagList.Add("[propName]", payment.RESERVATION.PROPERTY.propName);
            //tagList.Add("[propHyperlink]", payment.RESERVATION.PROPERTY.propHyperlink);

            string rt = "";
            DictData o;
            DateTime start = determineEarliestDate(properties);
            DateTime end = determineLatestDate(properties);
            int totalComplete = determineTotalComplete(properties);
            int totalPartial = determineTotalPartial(properties);

            tagList.Add("[resStartDate]", getDateString(start));
            tagList.Add("[resEndDate]", getDateString(end));

            template.sentenceList.TryGetValue("Introduction", out o);
            rt += o.value;

            if (properties.Count == 0)
            {
                //No properties were found for the date range. Just display a generic empty email.
                template.sentenceList.TryGetValue("No Properties Available", out o);
                rt += o.value;

                template.sentenceList.TryGetValue("Summary No Matches Found", out o);
                rt += o.value;
            }
            else
            {
                if (totalComplete > 0)
                {
                    //There is at least one complete match
                    if (totalComplete >= 2)
                    {
                        template.sentenceList.TryGetValue("Multiple Complete Matches Available", out o);
                        rt += o.value;
                        
                        foreach(PropertyMatch pm in properties.Where(p => !p.isPartial))
                        {
                            tagList.Clear();
                            tagList.Add("[resQuotedPrice]", formatAsCurrency(pm.quotedPrice));
                            tagList.Add("[resStartDate]", getDateString(pm.startDate));
                            tagList.Add("[resEndDate]", getDateString(pm.endDate));
                            tagList.Add("[quotedPrice]", formatAsCurrency(pm.quotedPrice));
                            tagList.Add("[propName]", pm.property.propName);
                            tagList.Add("[propHyperlink]", pm.property.propHyperlink);
                            
                            template.sentenceList.TryGetValue("Complete Match Property Information Model", out o);
                            rt += o.value;

                            foreach (KeyValuePair<string, string> kvp in tagList)
                                rt = rt.Replace(kvp.Key, kvp.Value);
                        }
                    }
                    else
                    {
                        //There was only one
                        template.sentenceList.TryGetValue("One Complete Match Available", out o);
                        rt += o.value;

                        template.sentenceList.TryGetValue("Complete Match Property Information Model", out o);
                        rt += o.value;

                        PropertyMatch pm = properties.Where(p => !p.isPartial).ToList()[0];
                        tagList.Clear();
                        tagList.Add("[resQuotedPrice]", formatAsCurrency(pm.quotedPrice));
                        tagList.Add("[resStartDate]", getDateString(pm.startDate));
                        tagList.Add("[resEndDate]", getDateString(pm.endDate));
                        tagList.Add("[quotedPrice]", formatAsCurrency(pm.quotedPrice));
                        tagList.Add("[propName]", pm.property.propName);
                        tagList.Add("[propHyperlink]", pm.property.propHyperlink);

                        foreach (KeyValuePair<string, string> kvp in tagList)
                            rt = rt.Replace(kvp.Key, kvp.Value);
                    }

                    if (totalPartial > 0)
                    {
                        //There was at least one partial match
                        if (totalPartial >= 2)
                        {
                            //There are two or more partial matches
                            template.sentenceList.TryGetValue("Complete and Partial Matches Available", out o);
                            rt += o.value;

                            foreach(PropertyMatch pm in properties.Where(p => p.isPartial))
                            {
                                tagList.Clear();
                                tagList.Add("[resQuotedPrice]", formatAsCurrency(pm.quotedPrice));
                                tagList.Add("[startDate]", getDateString(pm.startDate));
                                tagList.Add("[endDate]", getDateString(pm.endDate));
                                tagList.Add("[quotedPrice]", formatAsCurrency(pm.quotedPrice));
                                tagList.Add("[propName]", pm.property.propName);
                                tagList.Add("[propHyperlink]", pm.property.propHyperlink);
                                
                                template.sentenceList.TryGetValue("Complete-Partial Property Information Model", out o);
                                rt += o.value;

                                foreach (KeyValuePair<string, string> kvp in tagList)
                                    rt = rt.Replace(kvp.Key, kvp.Value);
                            }
                        }
                        else
                        {
                            //There was only one
                            template.sentenceList.TryGetValue("Complete and One Partial Match Available", out o);
                            rt += o.value;
                            
                            PropertyMatch pm = properties.Where(p => p.isPartial).ToList()[0];
                            tagList.Clear();
                            tagList.Add("[resQuotedPrice]", formatAsCurrency(pm.quotedPrice));
                            tagList.Add("[startDate]", getDateString(pm.startDate));
                            tagList.Add("[endDate]", getDateString(pm.endDate));
                            tagList.Add("[quotedPrice]", formatAsCurrency(pm.quotedPrice));
                            tagList.Add("[propName]", pm.property.propName);
                            tagList.Add("[propHyperlink]", pm.property.propHyperlink);
                                
                                template.sentenceList.TryGetValue("Complete-Partial Property Information Model", out o);
                                rt += o.value;

                                foreach (KeyValuePair<string, string> kvp in tagList)
                                    rt = rt.Replace(kvp.Key, kvp.Value);
                        }
                    }
                }//End to if not onlyPartial
                else
                {
                    //There were no complete matches, but there was at least one partial match
                    if(totalPartial >= 2)
                    {
                        //Two or more partial matches were found
                        template.sentenceList.TryGetValue("Multiple Partial Matches Available", out o);
                        rt += o.value;

                        foreach(PropertyMatch pm in properties.Where(p => p.isPartial))
                        {
                            tagList.Clear();
                            tagList.Add("[resQuotedPrice]", formatAsCurrency(pm.quotedPrice));
                            tagList.Add("[startDate]", getDateString(pm.startDate));
                            tagList.Add("[endDate]", getDateString(pm.endDate));
                            tagList.Add("[quotedPrice]", formatAsCurrency(pm.quotedPrice));
                            tagList.Add("[propName]", pm.property.propName);
                            tagList.Add("[propHyperlink]", pm.property.propHyperlink);

                            template.sentenceList.TryGetValue("Partial Match Property Information Model", out o);
                            rt += o.value;

                            foreach (KeyValuePair<string, string> kvp in tagList)
                                rt = rt.Replace(kvp.Key, kvp.Value);
                        }
                    }
                    else
                    {
                        //There was only one
                        template.sentenceList.TryGetValue("One Partial Match Available", out o);
                        rt += o.value;

                        PropertyMatch pm = properties.Where(p => p.isPartial).ToList()[0];
                        tagList.Clear();
                        tagList.Add("[resQuotedPrice]", formatAsCurrency(pm.quotedPrice));
                        tagList.Add("[startDate]", getDateString(pm.startDate));
                        tagList.Add("[endDate]", getDateString(pm.endDate));
                        tagList.Add("[quotedPrice]", formatAsCurrency(pm.quotedPrice));
                        tagList.Add("[propName]", pm.property.propName);
                        tagList.Add("[propHyperlink]", pm.property.propHyperlink);

                        template.sentenceList.TryGetValue("Partial Match Property Information Model", out o);
                        rt += o.value;

                        foreach (KeyValuePair<string, string> kvp in tagList)
                            rt = rt.Replace(kvp.Key, kvp.Value);
                    }//End to one partial
                }//End to only partial

                template.sentenceList.TryGetValue("Summary Matches Found", out o);
                rt += o.value;
            }//End to at least one match

            tagList.Add("[clientName]", clientName);

            template.sentenceList.TryGetValue("Signature", out o);
            rt += o.value;

            //Swap out any tags that are found
            foreach (KeyValuePair<string, string> kvp in tagList)
            {
                rt = rt.Replace(kvp.Key, kvp.Value);
            }

            return rt;
        }

        private static int determineTotalComplete(List<PropertyMatch> properties)
        {
            int total = 0;
            foreach (PropertyMatch pm in properties.Where(p => !p.isPartial))
                total += 1;

            return total;
        }

        private static int determineTotalPartial(List<PropertyMatch> properties)
        {
            int total = 0;
            foreach (PropertyMatch pm in properties.Where(p => p.isPartial))
                total += 1;

            return total;
        }
    }
}